AddCSLuaFile("cl_init.lua")
AddCSLuaFile("shared.lua")
AddCSLuaFile("weapon.lua")

include("shared.lua")
include("autorun/server/StargateExtras.lua")

ENT.cycleInterval = 0.1

local lastUseTime = 0

function ENT:SpawnFunction(player, trace)
	if(!trace.Hit) then 
    return 
  end
   
	local weapon = ents.Create(self:GetClass())
	weapon:SetPos(trace.HitPos + trace.HitNormal * weapon:BoundingRadius())
  weapon:SetVar("Owner", player)
	weapon:Spawn()
	weapon:Activate()
   
	undo.Create(self:GetClass())
	undo.AddEntity(weapon)
	undo.SetPlayer(player)
	undo.Finish()
end

function ENT:Initialize()
	if(util.IsValidModel(self.Entity:GetModel()) == false ||
    self.Entity:GetModel() == "models/error.mdl") then
    Msg("Using default model for "..self:GetClass()..".\n")
    self.Entity:SetModel(self.Weapon.DEFAULT_MODEL)
      
    if(util.IsValidModel(self.Entity:GetModel()) == false) then
       error("No valid model for "..self:GetClass()..".\n")
    end
  end
   
  self.Entity:SetSolid(SOLID_VPHYSICS)
	self.Entity:PhysicsInit(SOLID_VPHYSICS)
	
  local physics = self.Entity:GetPhysicsObject()
   
	if(physics && physics:IsValid()) then 
     physics:Wake()
  end
   
  -- Create looping sounds
  self.Weapon.Sounds["charging"] = CreateSound(self.Entity, DakaraWeapon.Sounds.Paths["charging"])
   
  -- Set resources
  if(self.HasRD) then
    self:AddResource("energy", 1)
    self:AddResource("ZPE", 1)
  end
    
    -- Set up wire inputs and outputs
	if(self.HasWire) then
		self:CreateWireInputs("Activate") 
		self:CreateWireOutputs("Charge", "Charge Required") 
	end
   
  StargateExtras:RegisterEntity(self)
   
  -- Set health
  self.Entity:SetMaxHealth(DakaraWeapon.MAX_HEALTH)
  self.Entity:SetHealth(DakaraWeapon.MAX_HEALTH)
  self.maxhealth = DakaraWeapon.MAX_HEALTH
  self.maxarmor = DakaraWeapon.MAX_ARMOUR
end

function ENT:Setup(targets, safeZoneRadius)
   if(self.Weapon == nil) then
      self.Weapon = DakaraWeapon:Create(targets, safeZoneRadius)
   else
      self.Weapon:Setup(targets, safeZoneRadius)
   end
end

function ENT:SetState(state)
   self.Weapon.state = state
   self.Entity:SetNetworkedInt("state", self.Weapon.state)
   self:SetAnimation(self.Weapon.Anims[state])
end

function ENT:SetAnimation(animationName)
   local animation = self.Entity:LookupSequence(animationName)
   
   if(animation ~= -1) then
      self.Entity:ResetSequence(animation)
   end
end

-- Respond to a given wire input
function ENT:TriggerInput(inputName, inputValue)
   if(inputName == "Activate") then
      if(inputValue ~= 0) then
         self:ChargeWeapon()
      else
         self:CancelCharging()
      end
   end
end

function ENT:ChargeWeapon()
   if(self.Weapon.state ~= self.Weapon.States.IDLE) then
      return false
   end
   
   if(self.HasRD) then
      if(self:IsEnergyAvailable() == false) then
         return false
      end
      
      local energyPerSec = math.ceil(self.Weapon.ENERGY_PER_SHOT / self.Weapon.CHARGE_TIME)
      
      -- Create energy capacitor capable of storing 2 seconds-worth of energy
      self:AddResource("energy", energyPerSec * 2)
      self:NextThink(CurTime() + 1)
   end
      
   self:SetState(self.Weapon.States.CHARGING)
   
   return true
end

function ENT:ChargeEnergy()
   if(self.Weapon.state ~= self.Weapon.States.CHARGING) then
      return false
   end
   
   local energyPerSec = self.Weapon.ENERGY_PER_SHOT / self.Weapon.CHARGE_TIME
   local energyNeeded = math.floor(energyPerSec * self.cycleInterval)
   
   if(self.HasRD) then
      local energyConsumed = self:ConsumeResource("energy", energyNeeded)
      
      if(energyConsumed < energyNeeded) then
         self:CancelCharging()
         return false
      end
      
      self.Weapon.energyCharged = self.Weapon.energyCharged + energyConsumed
   else
      self.Weapon.energyCharged = self.Weapon.energyCharged + energyNeeded
   end
   
   if(self.Weapon:IsCharged()) then
      self:FireWeapon()
   end
   
   return true
end

function ENT:DissipateChargedEnergy()
   if(self.Weapon.energyCharged == 0) then
      return false
   end
   
   local energyPerSec = self.Weapon.ENERGY_PER_SHOT / self.Weapon.CHARGE_TIME
   local energyDissipated = math.floor(energyPerSec * self.cycleInterval)
   self.Weapon.energyCharged = math.max(self.Weapon.energyCharged - energyDissipated, 0)
   return true
end

function ENT:CancelCharging()
   if(self.Weapon.state ~= self.Weapon.States.CHARGING) then
      return false
   end

   if(self.HasRD) then
      self:AddResource(self.Entity, "energy", 1)
   end

   local chargingSound = self.Weapon.Sounds["charging"]
   
   if(chargingSound) then
      chargingSound:FadeOut(1)
      
      timer.Simple(1, function() chargingSound:Stop() end, nil)
   end
   
   self:SetState(self.Weapon.States.IDLE)
   return true
end

function ENT:FireWeapon()
   if(self.Weapon.state ~= self.Weapon.States.CHARGING) then
      return false
   elseif(self.Weapon:IsCharged() == false) then
      return false
   end
   
   self:SetState(self.Weapon.States.FIRING)
   
   self:SpawnChargingEffect()

   local chargingSound = self.Weapon.Sounds["charging"]
   
   if(chargingSound) then
      chargingSound:Play()
   end
   
   timer.Simple(self.Weapon.EFFECT_DURATION, function() self:CreateWave() end, nil)
   
   return true
end

function ENT:SpawnChargingEffect()
   -- Should stop this effect from not showing sometimes.
   --timer.Simple(0.1, function()
      local effectInfo = EffectData()
      effectInfo:SetEntity(self.Entity)
      effectInfo:SetMagnitude(self.Weapon.EFFECT_DURATION)
      effectInfo:SetScale(3)
      util.Effect("dakara_charging", effectInfo)
   --end)
end

function ENT:CreateWave()
   local immuneEnts = {}
   
   if(self.Weapon.ARE_CONSTRAINED_ENTS_PROTECTED == true && 
      StarGate && StarGate.GetConstrainedEnts) then
      immuneEnts = StarGate.GetConstrainedEnts(self.Entity)
   end
   
   table.insert(immuneEnts, self.Entity)
   
   self.Wave = ents.Create("dakara_wave")
   self.Wave:Setup(self.Weapon, self.Entity:GetPos(), immuneEnts)
   self.Wave:Spawn()
   self.Wave:Activate()
   self.Wave:Start()
   
   local chargingSound = self.Weapon.Sounds["charging"]
   
   if(chargingSound) then
      chargingSound:Stop()
   end
   
   self.Weapon.energyCharged = 0
   self:SetState(self.Weapon.States.IDLE)
end

function ENT:Use()
   if(lastUseTime + 1 >= CurTime()) then
      return
   end
   
   self:ChargeWeapon()
   
   lastUseTime = CurTime()
end

function ENT:Think()
   self:NextThink(CurTime() + self.cycleInterval)
   self:SetOverlayText("")
   
   if(self.Weapon.state == self.Weapon.States.CHARGING) then
      self:ChargeEnergy()
      
      local percentCharged = math.floor((self.Weapon.energyCharged / self.Weapon.ENERGY_PER_SHOT) * 100)
      
      self:SetOverlayText(percentCharged.."% Charged")
   elseif(self.Weapon.state == self.Weapon.States.FIRING) then
      if(self:IsFiring() == false) then
         self:EndFiring()
      end
   elseif(self.Weapon.state == self.Weapon.States.IDLE) then
      self:DissipateChargedEnergy()
      
      if(self:IsEnergyAvailable() == false) then
         self:SetOverlayText("Requires "..self.Weapon.ENERGY_PER_SHOT.." energy.")
      elseif(self.Weapon.energyCharged > 0) then
         local percentCharged = math.floor((self.Weapon.energyCharged / self.Weapon.ENERGY_PER_SHOT) * 100)
         
         self:SetOverlayText(percentCharged.."% Charged")
      end
   end
   
   self:UpdateWireOutputs()
	return true
end

function ENT:IsEnergyAvailable()
   if(!self.HasRD) then
      return true
   end
   
   local energyAvailable = self:GetResource("energy")
   local zpeAvailable = self:GetResource("ZPE")
   
   local energyPerSec = math.floor(self.Weapon.ENERGY_PER_SHOT / self.Weapon.CHARGE_TIME)
   local energyNeeded = self.Weapon.ENERGY_PER_SHOT - self.Weapon.energyCharged
   
   if(energyAvailable >= energyPerSec) then
      return true
   elseif(zpeAvailable + energyAvailable >= energyNeeded) then
      return true
   else
      return false
   end
end

-- Updates all wire output values
function ENT:UpdateWireOutputs()
  if(!self.HasWire) then
    return false
  end
   
  self:SetWire("Charge", self.Weapon.energyCharged)
  self:SetWire(self.Entity, "Charge Required", self.Weapon.ENERGY_PER_SHOT)
   
  return true
end

function ENT:OnTakeDamage(damageInfo)  
  if(self.isDestructing == false && self.isCombatSystemEnabled ~= true) then
    self.Entity:TakePhysicsDamage(damageInfo)
     
    self.Entity:SetHealth(self.Entity:Health() - damageInfo:GetDamage())
      
    if(self.Entity:Health() <= 0) then
      self:Destruct()
    end
  end
end

function ENT:Destruct()
  if(self.isDestructing) then
    return
  end
   
  self.isDestructing = true
   
  local blastRadius = 500
  local blastDamage = 50 + (self.Weapon.energyCharged / self.Weapon.ENERGY_PER_SHOT * 1000)
   
  destructEffect = EffectData()
	destructEffect:SetOrigin(self.Entity:GetPos())
  destructEffect:SetRadius(blastRadius / 2)
	destructEffect:SetMagnitude(blastRadius / 10)
	util.Effect("Explosion", destructEffect, true, true)
   
  local owner = self.Entity:GetVar("Owner", self.Entity)
  util.BlastDamage(self.Entity, owner, self.Entity:GetPos(), blastRadius, blastDamage)
    
	self:Remove()
end

function ENT:OnRemove()
  self:CancelCharging()
   
  StargateExtras:UnregisterEntity(self)
   
  StarGate.WireRD.OnRemove(self)
end
